#include "printf.h"
#include "hal_api.h"
#include "common.h"
#include "io.h"
#include "bits.h"

#define  DMA_BUFFER_SIZE   1024

enum chan_priority {
	CHAN_PRIO_NORMAL,
	CHAN_PRIO_HIGH,
	CHAN_PRIO_TOP,
};

enum xfer_dir {
	XFER_DIR_MEM2MEM,
	XFER_DIR_MEM2DEV,
	XFER_DIR_DEV2MEM,
};


static __align(64) uint8_t dma_xfer_src_data[DMA_CHAN_MAXIMUM][DMA_BUFFER_SIZE];
static __align(64) uint8_t dma_xfer_dst_data[DMA_CHAN_MAXIMUM][DMA_BUFFER_SIZE];
static __IOM uint8_t xfer_done[DMA_CHAN_MAXIMUM] = { 0 };


void dma_transfer_handler(dma_chan_t chan, uint32_t status)
{
	(void)status;
	Hal_Dma_Data_Invalidate((uint32_t)&dma_xfer_dst_data[chan][0], DMA_BUFFER_SIZE);
	xfer_done[chan] = 1;
}

static void dma_data_init(int32_t chan)
{
	uint32_t i;
	for(i = 0; i < DMA_BUFFER_SIZE; i++) {
		dma_xfer_src_data[chan][i] = 0xA5;
	}
	for(i = 0; i < DMA_BUFFER_SIZE; i++) {
		dma_xfer_dst_data[chan][i] = 0xFF;
	}
}

static int32_t dma_xfer_config(int32_t chan, uint32_t srcaddr, uint32_t dstaddr, uint32_t len, enum xfer_dir dir,
			       uint32_t srcbrust, uint32_t dstbrust, Hal_Dma_Callback cb, void *args)
{
	int32_t ret;
	ret = Hal_Dma_Alloc_Chan(chan);
	if (ret) {
		printf("[DMA TEST]: dma chan %ld alloc err\r\n", chan);
		goto err;
	}
	ret = Hal_Dma_Config(chan, srcaddr, dstaddr, len, dir, srcbrust, dstbrust, 0, 0, 0, CHAN_PRIO_NORMAL);
	if (ret) {
		printf("[DMA TEST]: dma chan %ld config err\r\n", chan);
		goto err;
	}
	Hal_Dma_Register_Callback(chan, cb);
err:
	return ret;
}

static void xfer_start(uint8_t mask)
{
	Hal_Dma_Xfer_Multichan_Start(mask);
}

static void xfer_pend(int32_t chan)
{
	Hal_Dma_Xfer_Pending(chan);
}

static void xfer_suspend(int32_t chan)
{
	Hal_Dma_Xfer_Suspend(chan);
}

static void xfer_unsuspend(int32_t chan)
{
	Hal_Dma_Xfer_Unsuspend(chan);
}

static void xfer_abort(int32_t chan)
{
	Hal_Dma_Xfer_Abort(chan);
}

static void xfer_finish(int32_t chan)
{
	while(xfer_done[chan] == 0);
}

static void compare_xfer_data(int32_t chan)
{
	uint32_t i;
	for (i = 0; i < DMA_BUFFER_SIZE; i++) {
		if (dma_xfer_src_data[chan][i] != dma_xfer_dst_data[chan][i]) {
			printf("[DMA TEST]: dma chan %ld xfer data err\r\n", chan);
		}
	}
	printf("[DMA TEST]: dma chan %ld xfer 1024 data ok\r\n", chan);
}

static int32_t dma_m2m_test(uint8_t start_chan, uint8_t chan_nums, bool suspend, bool abort)
{
	uint32_t chan;
	int32_t ret;
	uint8_t mask;

	for (chan = start_chan; chan < start_chan + chan_nums; chan++) {
		dma_data_init(chan);
		ret = dma_xfer_config(chan, (uint32_t)&dma_xfer_src_data[chan][0], (uint32_t)&dma_xfer_dst_data[chan][0],
		                      DMA_BUFFER_SIZE, XFER_DIR_MEM2MEM, 4, 4, dma_transfer_handler, NULL);
		if (ret) {
			return ret;
		}
		xfer_pend(chan);
	}
	mask = GENMASK(start_chan + chan_nums - 1, start_chan);
	xfer_start(mask);
	if (suspend) {
		for (chan = start_chan; chan < start_chan + chan_nums; chan++) {
			xfer_suspend(chan);
		}
		Hal_Cnt_DelayMs(500);
		for (chan = start_chan; chan < start_chan + chan_nums; chan++) {
			xfer_unsuspend(chan);
		}
	}

	if (abort) {
		for (chan = start_chan; chan < start_chan + chan_nums; chan++) {
			xfer_abort(chan);
		}
		Hal_Cnt_DelayMs(500);
		xfer_start(mask);
	}
	for (chan = start_chan; chan < start_chan + chan_nums; chan++) {
		xfer_finish(chan);
		compare_xfer_data(chan);
	}
	return ret;
}


int32_t test_dma(void)
{
	int32_t ret;
	printf("\r\n[DMA]: TEST START\r\n");
	printf("[DMA]: dma transfer no suspend no abort\r\n");
	ret = dma_m2m_test(0, 8, false, false);
	Hal_Cnt_DelayMs(100);
	printf("[DMA]: dma transfer no abort but suspend\r\n");
	ret = dma_m2m_test(0, 8, true, false);
	Hal_Cnt_DelayMs(100);
	printf("[DMA]: dma transfer no suspend but abort\r\n");
	ret = dma_m2m_test(0, 8, false, true);
	Hal_Cnt_DelayMs(100);
	printf("[DMA]: dma transfer suspend and abort\r\n");
	ret = dma_m2m_test(0, 8, true, true);
	printf("[DMA]: TEST END\r\n");
	return ret;
}

